package com.example.pungolo.journal.view;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.DashPathEffect;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.RectF;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;

public class CroppableImageView extends View {
  private Bitmap bitmap;
  private RectF bitmapRect = new RectF();
  private RectF defaultBitmapRect = new RectF();
  private RectF cropRect = new RectF();
  private Rect destination = new Rect();
  private Rect source = new Rect();
  private RectF screenRect = new RectF();
  private RectF tempRect = new RectF();
  private Paint cropRectPaint;
  private Paint cropCirclePaint;
  private PointF starting1 = new PointF();
  private PointF starting2 = new PointF();
  private RectF startingBitmapRect = new RectF();
  private int pointer1 = -1;
  private int pointer2 = -1;
  private int state = STATE_IDLE;

  private static final int STATE_IDLE = 0;
  private static final int STATE_MOVING_RECTANGLE = 1;
  private static final int STATE_RESIZING_RECTANGLE = 2;
  private static final int STATE_MOVING_BITMAP = 3;
  private static final int STATE_RESIZING_BITMAP = 4;

  private static final int MINIMUM_CROP_WIDTH = 150;
  private static final int MINIMUM_CROP_HEIGHT = 100;

  public CroppableImageView(Context context) {
    super(context);
    init();
  }

  public CroppableImageView(Context context, AttributeSet attrs) {
    super(context, attrs);
    init();
  }

  public CroppableImageView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    init();
  }

  private void init() {
    tempRect = new RectF();
    destination = new Rect();
    source = new Rect();
    bitmapRect = new RectF();
    cropRect = new RectF();
    screenRect = new RectF();
    defaultBitmapRect = new RectF();
    startingBitmapRect = new RectF();

    cropRectPaint = new Paint();
    cropRectPaint.setColor(Color.BLUE);
    cropRectPaint.setStyle(Style.STROKE);
    cropRectPaint.setPathEffect(new DashPathEffect(new float[] { 10, 20 }, 0));
    cropRectPaint.setStrokeWidth(3);

    cropCirclePaint = new Paint();
    cropCirclePaint.setColor(Color.BLUE);
    cropCirclePaint.setStyle(Style.FILL);
  }

  @Override
  protected void onSizeChanged(int w, int h, int oldw, int oldh) {
    super.onSizeChanged(w, h, oldw, oldh);
    screenRect.right = w;
    screenRect.bottom = h;
    if (w > 0 && h > 0)
      resetDefaultPosition();
  }

  public void setBitmap(Bitmap bitmap) {
    this.bitmap = bitmap;
    resetDefaultPosition();
  }

  private void resetDefaultPosition() {
    screenRect.left = 0;
    screenRect.top = 0;
    screenRect.right = this.getWidth();
    screenRect.bottom = this.getHeight();

    if (bitmap != null) {
      float xScale = screenRect.right / bitmap.getWidth();
      float yScale = screenRect.bottom / bitmap.getHeight();
      float scale = Math.min(xScale, yScale);
      float w = bitmap.getWidth() * scale;
      float h = bitmap.getHeight() * scale;

      bitmapRect.left = (screenRect.right - w) / 2;
      bitmapRect.top = (screenRect.bottom - h) / 2;
      bitmapRect.right = bitmapRect.left + w;
      bitmapRect.bottom = bitmapRect.top + h;
      defaultBitmapRect.set(bitmapRect);
    }

    cropRect.left = screenRect.right / 4;
    cropRect.top = screenRect.bottom / 4;
    cropRect.right = cropRect.left * 3;
    cropRect.bottom = cropRect.top * 3;

    cropRect.setIntersect(cropRect, bitmapRect);

    updateSrcDstRects();
  }

  @Override
  public boolean onTouchEvent(MotionEvent event) {
    int action = event.getAction() & MotionEvent.ACTION_MASK;
    int pointerIndex = (event.getAction() & MotionEvent.ACTION_POINTER_INDEX_MASK) >> MotionEvent.ACTION_POINTER_INDEX_SHIFT;
    int pointer = event.getPointerId(pointerIndex);
    float x = event.getX(pointerIndex);
    float y = event.getY(pointerIndex);
    
    if (action != MotionEvent.ACTION_MOVE)
      Log.i("pungolo-journal", "Pointer " + pointer + " has gone " + action);
    
    boolean isNearCTL = Math.pow(x-cropRect.left, 2) + Math.pow(y-cropRect.top, 2) <= 50*50;
    boolean isNearCBR = Math.pow(x-cropRect.right, 2) + Math.pow(y-cropRect.bottom, 2) <= 50*50;
    
    if (action == MotionEvent.ACTION_DOWN) {
      if (isNearCTL) {
        pointer1 = pointer;
        state = STATE_RESIZING_RECTANGLE;
      } else if (isNearCBR) {
        pointer2 = pointer;
        state = STATE_RESIZING_RECTANGLE;
      } else if (cropRect.contains(x,y)) {
        pointer1 = pointer;
        starting1.x = x - cropRect.left;
        starting1.y = y - cropRect.top;
        state = STATE_MOVING_RECTANGLE;
      } else {
        pointer1 = pointer;
        starting1.x = x - bitmapRect.left;
        starting1.y = y - bitmapRect.top;
        state = STATE_MOVING_BITMAP;
      }
    } else if (action == MotionEvent.ACTION_POINTER_DOWN) {
      if (state == STATE_RESIZING_RECTANGLE) {
        if (isNearCTL && pointer1 == -1) {
          pointer1 = pointer;
          starting1.x = cropRect.left;
          starting1.y = cropRect.top;
        } else if (isNearCBR && pointer2 == -1) {
          pointer2 = pointer;
          starting2.x = cropRect.right;
          starting2.y = cropRect.bottom;
        }
      }
      
      if (state == STATE_MOVING_BITMAP) {
        pointer2 = pointer;
        starting2.x = x - bitmapRect.left;
        starting2.y = y - bitmapRect.top;
        startingBitmapRect.set(bitmapRect);
        state = STATE_RESIZING_BITMAP;
      }
    } else if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) {
      pointer1 = -1;
      pointer2 = -1;
      state = STATE_IDLE;
    } else if (action == MotionEvent.ACTION_POINTER_UP) {
      if (pointer1 != pointer && pointer2 != pointer) return false;
      
      if (state == STATE_RESIZING_RECTANGLE) {
        if (pointer2 == pointer) {
          pointer2 = -1;
        } else {
          pointer1 = -1;
        }
      } else if (state == STATE_RESIZING_BITMAP) {
        state = STATE_MOVING_BITMAP;
        if (pointer1 == pointer) pointer1 = pointer2;
        pointer2 = -1;
        int index = event.findPointerIndex(pointer1);
        starting1.x = event.getX(index) - bitmapRect.left;
        starting1.y = event.getY(index) - bitmapRect.top;
      } else {
        if (pointer2 != -1) throw new IllegalStateException();
        int index = 0;
        if (pointer1 == event.getPointerId(0))
          index = 1;
        pointer1 = event.getPointerId(index);
        starting1.x = (event.getX(index) - cropRect.left);
        starting1.y = (event.getY(index) - cropRect.top);
      }
    } else if (action == MotionEvent.ACTION_MOVE) {
      
      if (state == STATE_RESIZING_RECTANGLE) {
        if (pointer1 != -1) {
          int index = event.findPointerIndex(pointer1);
          cropRect.left = clamp(event.getX(index), bitmapRect.left, bitmapRect.right);
          cropRect.top = clamp(event.getY(index), bitmapRect.top, bitmapRect.bottom);
          if (cropRect.width() < MINIMUM_CROP_WIDTH) cropRect.left = cropRect.right-MINIMUM_CROP_WIDTH;
          if (cropRect.height() < MINIMUM_CROP_HEIGHT) cropRect.top = cropRect.bottom-MINIMUM_CROP_HEIGHT;
        }
        if (pointer2 != -1) {
          int index = event.findPointerIndex(pointer2);
          cropRect.right = clamp(event.getX(index), bitmapRect.left, bitmapRect.right);
          cropRect.bottom = clamp(event.getY(index), bitmapRect.top, bitmapRect.bottom);
          if (cropRect.width() < MINIMUM_CROP_WIDTH) cropRect.right = cropRect.left+MINIMUM_CROP_WIDTH;
          if (cropRect.height() < MINIMUM_CROP_HEIGHT) cropRect.bottom = cropRect.top+MINIMUM_CROP_HEIGHT;
        }
      }
      
      if (state == STATE_MOVING_RECTANGLE) {
        float w = cropRect.width();
        float h = cropRect.height();
        int index = event.findPointerIndex(pointer1);
        cropRect.left = event.getX(index) - starting1.x;
        cropRect.top = event.getY(index) - starting1.y;
        cropRect.right = cropRect.left + w;
        cropRect.bottom = cropRect.top + h;
      }

      if (state == STATE_MOVING_BITMAP) {
        float w = bitmapRect.width();
        float h = bitmapRect.height();
        int index = event.findPointerIndex(pointer1);
        bitmapRect.left = event.getX(index) - starting1.x;
        bitmapRect.top = event.getY(index) - starting1.y;
        if (bitmapRect.left > defaultBitmapRect.left) bitmapRect.left = defaultBitmapRect.left;
        if (bitmapRect.top > defaultBitmapRect.top) bitmapRect.top = defaultBitmapRect.top;
        if (bitmapRect.left + w < defaultBitmapRect.right) bitmapRect.left = defaultBitmapRect.right - w;
        if (bitmapRect.top + h < defaultBitmapRect.bottom) bitmapRect.top = defaultBitmapRect.bottom - h;
        bitmapRect.right = bitmapRect.left + w;
        bitmapRect.bottom = bitmapRect.top + h;
        updateSrcDstRects();
      }

      if (state == STATE_RESIZING_BITMAP) {
        int index1 = event.findPointerIndex(pointer1);
        float x1 = event.getX(index1);
        float y1 = event.getY(index1);
        int index2 = event.findPointerIndex(pointer2);
        float x2 = event.getX(index2);
        float y2 = event.getY(index2);
        float mx = (x1 + x2) / 2;
        float my = (y1 + y2) / 2;
        float odx = starting2.x - starting1.x;
        float ody = starting2.y - starting1.y;
        float omx = starting1.x + odx/2;
        float omy = starting1.y + ody/2;
        float ow = startingBitmapRect.width();
        float oh = startingBitmapRect.height();
        
        float d = (float) Math.sqrt((x2-x1)*(x2-x1)+(y2-y1)*(y2-y1));
        float od = (float)Math.sqrt(odx*odx+ody*ody);
        float scale = d/od;
        float minscale = defaultBitmapRect.width() / startingBitmapRect.width();
        scale = Math.max(scale, minscale);
        float w = ow * scale;
        float h = oh * scale;
        
        bitmapRect.left = mx - omx * scale;
        bitmapRect.top = my - omy * scale;
        
        if (bitmapRect.left > defaultBitmapRect.left) bitmapRect.left = defaultBitmapRect.left;
        if (bitmapRect.top > defaultBitmapRect.top) bitmapRect.top = defaultBitmapRect.top;
        if (bitmapRect.left + w < defaultBitmapRect.right) bitmapRect.left = defaultBitmapRect.right - w;
        if (bitmapRect.top + h < defaultBitmapRect.bottom) bitmapRect.top = defaultBitmapRect.bottom - h;
        bitmapRect.right = bitmapRect.left + w;
        bitmapRect.bottom = bitmapRect.top + h;
        updateSrcDstRects();
      }

      reposition(cropRect, bitmapRect);
      reposition(cropRect, screenRect);
    }
    invalidate();
    return true;
  }
  
  private static float clamp(float a, float min, float max) {
    return Math.max(min, Math.min(a, max));
  }
  
  private static void reposition(RectF inner, RectF outer) {
    if (inner.left < outer.left) inner.offset(outer.left-inner.left, 0);
    if (inner.top < outer.top) inner.offset(0, outer.top-inner.top);
    if (inner.right > outer.right) inner.offset(outer.right-inner.right, 0);
    if (inner.bottom > outer.bottom) inner.offset(0, outer.bottom-inner.bottom);
  }

  private void updateSrcDstRects() {
    if (bitmap == null || screenRect.width() == 0 || screenRect.height() == 0 || bitmapRect.width() == 0 || bitmapRect.height() == 0) {
      return;
    }

    float bmScale = bitmapRect.width() / bitmap.getWidth();
    if (tempRect.setIntersect(bitmapRect, screenRect)) {
      tempRect.round(destination);
      source.left = (int) ((tempRect.left - bitmapRect.left) / bmScale);
      source.top = (int) ((tempRect.top - bitmapRect.top) / bmScale);
      source.right = source.left + (int) (tempRect.width() / bmScale);
      source.bottom = source.top + (int) (tempRect.height() / bmScale);
    } else {
      throw new IllegalStateException("The bitmap isn't in the screen!");
    }
  }
  
  public void getCropData(int[] cropData) {
    float scale = bitmap.getWidth() / bitmapRect.width();
    cropData[0] = (int) ((cropRect.left - bitmapRect.left) * scale);
    cropData[1] = (int) ((cropRect.top - bitmapRect.top) * scale);
    cropData[2] = (int) (cropRect.width() * scale);
    cropData[3] = (int) (cropRect.height() * scale);
  }

  @Override
  public void draw(Canvas canvas) {
    super.draw(canvas);
    if (bitmap == null)
      return;
    canvas.drawBitmap(bitmap, source, destination, null);
    canvas.drawRect(cropRect, cropRectPaint);
    canvas.drawCircle(cropRect.left, cropRect.top, 25, cropCirclePaint);
    canvas.drawCircle(cropRect.right, cropRect.bottom, 25, cropCirclePaint);
  }

}
